<?php
/**
 * Created by IntelliJ IDEA.
 * User: xinkon
 * Date: 17-12-21
 * Time: 下午9:16
 */

namespace relay;

class Swoole
{
    private $server;

    protected $bind_ip = '0.0.0.0';
    protected $bind_port = 5000;
    protected $swoole_config = [
        'worker_num' => 20,
        'daemonize' => false,
    ];

    protected $db_config = [
        'host' => '127.0.0.1',
        'dbname' => '',
        'username' => '',
        'passwd' => ''
    ];

    ##redis
    protected $redis_host = '127.0.0.1';
    protected $redis_port = 6379;
    protected $redis_passwd = '';
    protected $redis_db = 6;

    public function __construct($bind_ip = '', $swoole_config = [], $bind_port = '', $db_config = [], $redis_config = [])
    {
        if ($bind_ip) $this->bind_ip = $bind_ip;
        if ($bind_port) $this->bind_port = $bind_port;
        if (empty($db_config)) {
            throw new \Exception("数据库配置不能为空");
        } else {
            $this->swoole_config = array_merge($this->swoole_config, $swoole_config);
        }
        if (!empty($swoole_config)) {
            $this->db_config = array_merge($this->db_config, $db_config);
        }
        if (!empty($redis_config)) {
            if (isset($redis_config['redis_host'])) $this->redis_host = $redis_config['redis_host'];
            if (isset($redis_config['redis_port'])) $this->redis_port = $redis_config['redis_port'];
            if (isset($redis_config['redis_passwd'])) $this->redis_host = $redis_config['redis_passwd'];
            if (isset($redis_config['redis_db'])) $this->redis_host = $redis_config['redis_db'];
        }
        $this->server = new    \swoole_server($this->bind_ip, $this->bind_port, SWOOLE_BASE);

        $this->server->set($this->swoole_config);
        $eventList = ['Start', 'ManagerStart', 'ManagerStop', 'PipeMessage', 'Task',
            'Packet', 'Finish', 'Receive', 'Connect', 'Close', 'Timer', 'WorkerStart', 'WorkerStop', 'Shutdown', 'WorkerError'];
        // 设置回调
        foreach ($eventList as $event) {
            if (method_exists($this, 'on' . $event)) {
                $this->server->on($event, [$this, 'on' . $event]);
            }
        }
    }

    /**
     * 启动服务
     */
    public function start()
    {
        $this->server->start();
    }

    /**
     * 启动时
     * @param \swoole_server $serv
     */
    public function onStart(\swoole_server $serv)
    {

        echo "Start\n";
    }

    /**
     * 客户端链接成功
     * @param \swoole_server $serv
     * @param $fd
     * @param $from_id
     */
    public function onConnect(\swoole_server $serv, $fd, $from_id)
    {
        $this->show_log($fd . '上线连接');
    }

    protected function show_log($str)
    {
        print_r(date('Y-m-d H:i:s') . ':' . (is_array($str) ? json_encode($str) : $str) . PHP_EOL);
        return true;
    }

    /**
     * 当进程以外中断,重新打开需要走的流程,把未完成的任务放回计时器
     * 链接redis
     * @param \swoole_server $serv
     * @param $id
     */
    public function onWorkerstart(\swoole_server $serv, $id)
    {
        $redis = new \redis;
        $redis->connect($this->redis_host, $this->redis_port);
        if (!empty($this->redis_passwd)) {
            $redis->auth($this->redis_passwd);
        }
        $serv->redis = $redis;
        $serv->redis->select($this->redis_db);##存放客户端
        $pdo = new \PDO("mysql:host=" . $this->db_config['host'] . ";dbname=" . $this->db_config['dbname'] . "", "" . $this->db_config['username'] . "", "" . $this->db_config['passwd'] . "", array(
            \PDO::ATTR_PERSISTENT => true
        ));
        $serv->pdo = $pdo;


    }

    /**
     * 收到消息
     * @param \swoole_server $serv
     * @param $fd
     * @param $from_id
     * @param $msg
     */
    public function onReceive(\swoole_server $serv, $fd, $from_id, $msg)
    {
        if (is_array(json_decode($msg, true))) { ##服务器内部请求
            $msg = json_decode($msg, true);
            $which = isset($msg['which']) ? $msg['which'] : '01';  ##路号
            $action = isset($msg['action']) ? $msg['action'] : 'open'; ##动作
            $id = $msg['id']; ##设备id
            $uid = $msg['uid']; ##用户uid
            $c_fd = $this->get_client($serv, $id);  ##获取设备的连接id
            if (!$c_fd || $serv->connection_info($c_fd) == false) {
                return $this->result($serv, $fd, 1, '设备不在线');
            }
            $msg['server_client'] = $fd;  ##回复的客户端id
            $msg['time'] = time();  ##任务时间戳
            ##把任务写入redis
            if (!$serv->connection_info($c_fd)) {
                return $this->result($serv, $fd, 0, '设备不在线..');
            }
            if ($action == 'open' || $action == 'close') {
                $task = $this->get_task($serv, $id, 'open', $which);
                if ($task && $task['uid'] != $uid) {
                    return $this->result($serv, $fd, -1, '其他用户正在使用中');
                }
                if ($task && $action == 'open') {
                    return $this->result($serv, $fd, 0, '开启状态中..');
                }

            }

            if ($this->operation($serv, $c_fd, $which, $action)) {##向客户端发送指令成功
                if ($action == 'open') {
                    $this->deduction($serv, $fd, $msg);
                }
                $this->add_task($serv, $id, $action, $which, $msg);
            };
        } else {
            $msg_new = bin2hex($msg);
            $this->show_log('客户端返回数据:' . $msg_new);

            if ($msg_new) {
                $id = $serv->redis->get($fd);
                if (strlen($msg_new) == 16) {##注册id,心跳保存
                    $id = substr($msg_new, 6);
                    $this->save_client($serv, $id, $fd);
                    if ('aaaaaa' == substr($msg_new, 0, 6)) {##当设备新注册进来,获取开关状态
                        swoole_timer_tick(600000, function () use ($serv, $fd) {  ##设置定时器,没分钟获取设备信息
                            $this->operation($serv, $fd, '01', 'status');
                        });
                        $this->operation($serv, $fd, '01', 'status');
                    } else {##心跳
                        $task_01 = $this->get_task($serv, $id, 'open', '01');##获取任务
                        $task_02 = $this->get_task($serv, $id, 'open', '02');##获取任务
                        if ($task_01) {
                            $this->deduction($serv, $fd, $task_01);
                        }
                        if ($task_02) {
                            $this->deduction($serv, $fd, $task_02);
                        }
                    }
                    $serv->send($fd, $msg);
                };
                if (strlen($msg_new) == 34) { ##控制命令返回
                    $action_tem = substr($msg_new, 20, 2);
                    $which = substr($msg_new, 18, 2);
                    if ($action_tem == '55') {
                        $action = 'open';
                    } elseif ($action_tem == '22') {
                        $action = 'close';
                    } else {
                        return false;
                    }
                    if (!$id) {
                        return false;
                    }
                    $task = $this->get_task($serv, $id, $action, $which);##获取任务
                    if ($task) {
                        if ($action == 'open' and $task['times'] > 0) {
                            $which = $task['which'];
                            $time = $task['time']; ##打开的时间戳
                            $uid = $task['uid'];
                            $tick = \swoole_timer::after($task['times'] * 1000 + rand(10, 1000), function () use ($serv, $fd, $which, $id, $time, $uid) {
                                $task = json_decode($serv->redis->get($id . '_' . $which . '_open_task'), true);
                                $this->show_log($task);
                                if (!empty($task)) {
                                    if ($task['uid'] == $uid && $task['time'] == $time) {
                                        if ($this->operation($serv, $fd, $which, 'close')) {
                                            $serv->redis->delete($id . '_' . $which . '_open_task');
                                        }
                                    }
                                }
                            });

                            $this->show_log($tick . ':' . ($task['times']) . '秒后关闭');
                        }
                    }
                    if ($action == 'close') {
                        $serv->redis->delete($id . '_' . $which . '_open_task');
                    }
                    return $this->result($serv, $task['server_client'], 0, '操作成功');
                }
                ##dccdaaaae0b10018e3010000000000e2010000000002cddc
                if (strlen($msg_new) == 48) { ##获取状态
                    if (!$id) {
                        return false;
                    }
                    $action = substr($msg_new, 10, 2);
                    $task = $serv->redis->get($id . '_status_task');
                    $task = json_decode($task, true);
                    if ($action == 'b1') {##获取状态
                        $status = $this->parsing($msg_new);
//                        print_r($status);
                        foreach ($status as $key => $val) {
                            $val = $val == 1 ? 'open' : 'close';
                            $key = '0' . $key;
                            $t = $this->get_task($serv, $id, 'open', $key);
                            if ($val == 'open' && empty($t)) {
                                swoole_timer_after(3000, function () use ($serv, $fd) {
                                    $this->operation($serv, $fd, '01', 'status');
                                });
                                $this->operation($serv, $fd, $key, 'close');
                            }
                            if ($val == 'open' && !empty($t)) {
                                $times = $this->getTimes($t);
                                if ($times > 0) {
                                    swoole_timer_after($times * 1000, function () use ($serv, $fd, $key) {
                                        $this->operation($serv, $fd, $key, 'close');
                                    });
                                }
                                if (($times == 0 && $t['times'] > 0) || $times < 0) {
                                    $this->operation($serv, $fd, $key, 'close');
                                }

                            }
                            if ($val == 'close' && !empty($t)) {
                                $t['times'] = $times = $this->getTimes($t);
                                if ($times > 0) {
                                    $this->add_task($serv, $id, 'open', $key, $t);
                                    swoole_timer_after(3000, function () use ($serv, $fd) {
                                        $this->operation($serv, $fd, '01', 'status');
                                    });
                                    $this->operation($serv, $fd, $key, 'open');
                                }
                            }
                        }
                        $this->result($serv, $task['server_client'], 0, '获取成功', $status);
                    }
                    if ($action == 'c1') {##关闭按钮
                        $status = $this->parsing_close($msg_new);
                        if ($status[1] == 1) {
                            $serv->redis->delete($id . '_01_open_task');
                            $serv->redis->delete($id . '_02_open_task');
                        }
//                        $this->show_log($msg_new);
                    }

                }
            }

        }
    }

    /**
     * 执行扣费
     * @param \swoole_server $serv
     * @param array $task
     */
    public function deduction(\swoole_server $serv, $fd, array $task = [])
    {
        try {
            $uid = $task['uid'];
            $time = time();
            $start_time = $task['time'];
            $money = $task['minute_money'];
            $device_id = $task['id'];
            $mint = (int)(($time - $start_time) / 60);
            $msg = '';
            if ($time - $start_time < 60) {
                $money = round($money * (($time - $start_time) / 60), 2);
                $msg .= '(当前不足一分钟,将按比例扣除)';
            }
            $msg .= '设备编号:' . $device_id . ',执行扣费:' . $money . '元,扣费时间:' . date('Y-m-d H:i:s', $time) . ',当前已运行:' . ($mint == 0 ? (($time - $start_time) . '秒') : ($mint . '分' . ($time - $start_time) % 60 . '秒'));
            $serv->pdo->beginTransaction();
            $rs = $serv->pdo->query("SELECT * FROM h_user_member WHERE uid={$uid}");
            $user = $rs->fetch(\PDO::FETCH_ASSOC);
            $json_task = json_encode($task);
            if (!empty($uid) && $user['yue'] > $money) {
                $serv->pdo->exec("UPDATE h_user_member SET h_user_member.yue=h_user_member.yue-{$money} WHERE h_user_member.uid={$uid}");
                $serv->pdo->exec("INSERT INTO h_deduction_log (device_id, uid, money, msg,task, create_time) VALUE ('{$device_id}',{$uid},{$money},'{$msg}','{$json_task}',{$time})");
            } else {
                $serv->pdo->commit();
                return $this->operation($serv, $fd, '01', 'close');
            }
            $serv->pdo->commit();
            return true;
        } catch (\Exception $e) {
            print_r($e);
        }

    }

    /**
     *获取客户端
     * @param bool $id ($fd,或者$id)
     * @return bool|string
     */
    public function get_client(\swoole_server $server, $id = false)
    {
        if ($id == false) {
            return false;
        }
        return $server->redis->get($id);
    }

    /**
     * 返回参数
     * @param \swoole_server $server
     * @param $fd
     * @param int $code
     * @param string $msg
     * @param array $data
     * @return bool
     */
    public function result(\swoole_server $server, $fd, $code = 0, $msg = '', $data = [])
    {
        if ($server->connection_info($fd)) {
            return $server->send($fd, json_encode([
                'code' => $code,
                'msg' => $msg,
                'data' => $data,
            ]));
        }
        return true;
    }

    /**
     * 获取任务
     * @param $serv
     * @param $id
     * @param $action
     * @return mixed
     */
    public function get_task($serv, $id, $action, $which)
    {
        return json_decode($serv->redis->get($id . '_' . $which . '_' . $action . '_task'), true);
    }

    /**
     * 控制电源
     * @param \swoole_server $server
     * @param $fd
     * @param $which $$第几路 01,02,03,04
     * @param string $action ##动作, open打开,close关闭,status获取状态
     * @return string
     */
    public function operation(\swoole_server $server, $fd, $which = '01', $action = 'open')
    {
        $cmd = 'DCCDAAAAE0A10011E1{%which%}{%action%}00000000CDDC';
        switch ($action) {
            case 'open':
                $cmd = str_replace('{%which%}', $which, $cmd);
                $cmd = str_replace('{%action%}', '55', $cmd);
                break;
            case 'close':
                $cmd = str_replace('{%which%}', $which, $cmd);
                $cmd = str_replace('{%action%}', '22', $cmd);
                break;
            case 'status':
                $cmd = 'DCCDAAAAAAB1001101010101010101CDDC';
                break;
        }
        $this->show_log('发送数据:' . $cmd);
        return $server->send($fd, hex2bin($cmd));
    }

    /**
     * 添加任务
     * @param $serv
     * @param $id
     * @param $action
     * @param $task
     * @return mixed
     */
    public function add_task($serv, $id, $action, $which, $task)
    {
        return $serv->redis->set($id . '_' . $which . '_' . $action . '_task', json_encode($task));
    }

    /**
     * 存放客户端
     * @param $id
     * @param $fd
     * @param int $time
     */
    public function save_client(\swoole_server $server, $id, $fd, $time = 50)
    {
        $server->redis->set($id, $fd, $time);
        $server->redis->set($fd, $id, $time);
        return true;
    }

    /**
     * 解析继电器状态
     * @param $str
     */
    public function parsing($str = '')
    {
        if (empty($str)) {
            return false;
        }
        $e2 = substr($str, 30, 14);
        $status = [
            1 => 0,
            2 => 0,
        ];
        switch ($e2[-1]) {
            case 0:
                break;
            case 1:
                $status[1] = 1;
                break;
            case 2:
                $status[2] = 1;
                break;
            case 3:
                $status[2] = 1;
                $status[1] = 1;
                break;
        }
        return $status;
    }

    public function getTimes(array $task)
    {
        if ($task['times'] == 0) {
            return 0;
        }
        $times = $task['times'] + $task['time'] - time() + 10;
        $this->show_log('剩余时间:' . $times . PHP_EOL);
        return $times < 0 ? -1 : $times;
    }

    /**
     * 解析继电器状态
     * @param $str
     */
    public function parsing_close($str = '')
    {
        if (empty($str)) {
            return false;
        }
        $e3 = substr($str, 30, 14);
        $status = [
            1 => 0,
            2 => 0,
        ];
        switch ($e3[-1]) {
            case 0:
                break;
            case 1:
                $status[1] = 1;
                break;
            case 2:
                $status[2] = 1;
                break;
            case 3:
                $status[2] = 1;
                $status[1] = 1;
                break;
        }
        return $status;
    }

    /**
     * 链接关闭
     * @param \swoole_server $serv
     * @param $fd
     * @param $from_id
     * @return bool
     */
    public function onClose(\swoole_server $serv, $fd, $from_id)
    {
        $this->show_log($fd . '断开了');
        return true;
    }

}